#pragma once

#include "event_base.hpp"
#include "module.hpp"
#include "win_handle.hpp"

namespace winboost {
   namespace events {

class event_router : public boost::noncopyable
{
public:
   void set_command_handler(UINT cmd_id, sig_cmd_event_t::slot_type fn);
   void set_command_handler_ui(UINT cmd_id, sig_update_ui_event_t::slot_type fn);
   void set_message_handler(UINT message, sig_msg_event_t::slot_type fn);
   void set_notify_handler(UINT message, sig_nfy_event_t::slot_type fn); 

   virtual LRESULT route(UINT message, WPARAM wparam, LPARAM lparam);

protected:
   //-------------------------------------------
   // Special case message cracker for specialized 
   // window controls. If handled = 'true' then message is
   // no longer routed.
   //-------------------------------------------
   virtual bool pre_translate_message(UINT message, WPARAM wparam, LPARAM lparam){ return false; }
   virtual LRESULT default_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { return 0L; }
   virtual HWND handle(void) const { return NULL; }

   static LRESULT CALLBACK cbt_hook(int code, WPARAM wParam, LPARAM lParam);
   static LRESULT CALLBACK wnd_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
   static bool check_exit_state(HWND hwnd, UINT message);

private:
   map_commands_t    m_commands;
   map_messages_t    m_messages;
   map_notify_t      m_notifications;
   map_update_ui_t   m_commands_ui; // user interface update commands

   template<typename _MapType, typename _Func, typename _Sig>
   void set_handler(_MapType& m, UINT id, typename _Sig::slot_type fn);
   
   LRESULT handle_command_ui(UINT message, WPARAM wparam, LPARAM lparam);
   LRESULT handle_command(UINT message, WPARAM wparam, LPARAM lparam);
   LRESULT handle_notification(UINT message, WPARAM wparam, LPARAM lparam);
   LRESULT handle_message(UINT message, WPARAM wparam, LPARAM lparam);
};

   

//-------------------------------------
// implementation
//-------------------------------------

//-------------------------------------
// We can assign event to multiple handlers
//-------------------------------------
template<typename _MapType, typename _Func, typename _Sig>
void event_router::set_handler(_MapType& m, UINT id, typename _Sig::slot_type fn)
{
   _MapType::iterator it = m.find(id);
   _Func pevent;

   if(it == m.end())
   {
      pevent = _Func(new _Sig);
      m[id] = pevent;
   }
   else
      pevent = it->second;

   pevent->connect(fn);
}

inline void event_router::set_command_handler(UINT cmd_id, sig_cmd_event_t::slot_type fn)
{
   set_handler<map_commands_t, cmd_event_func_ptr_t, sig_cmd_event_t>
      (m_commands, cmd_id, fn);
}

inline void event_router::set_command_handler_ui(UINT cmd_id, sig_update_ui_event_t::slot_type fn)
{
   map_update_ui_t::iterator it = m_commands_ui.find(cmd_id);

#ifdef _DEBUG
   if(it != m_commands_ui.end())
   {
      debug::trace_dbg_t TRACE;
      TRACE << "**** winboost Warning! Command UI handler " << cmd_id 
         << " already exists! Overwriting it with new value." << std::endl;
      TRACE.print();
   }
#endif // _DEBUG

   update_ui_event_func_ptr_t pevent = update_ui_event_func_ptr_t(new sig_update_ui_event_t);
   pevent->connect(fn);
   m_commands_ui[cmd_id] = pevent;
}

inline void event_router::set_message_handler(UINT message, sig_msg_event_t::slot_type fn)
{
   set_handler<map_messages_t, msg_event_func_ptr_t, sig_msg_event_t>
      (m_messages, message, fn);
}

inline void event_router::set_notify_handler(UINT message, sig_nfy_event_t::slot_type fn)
{
   set_handler<map_notify_t, nfy_event_func_ptr_t, sig_nfy_event_t>
      (m_notifications, message, fn);
}

inline LRESULT CALLBACK event_router::cbt_hook(int code, WPARAM wParam, LPARAM lParam)
{
   state::state_ptr_type pstate = _the_module.get_thread_state();

   if (code==HCBT_CREATEWND)
   {
      HWND hwnd = (HWND)wParam;
      if(pstate->m_pwnd != NULL)
      {
         resources::win_handle* pwin = pstate->m_pwnd;
         _the_module.add_window(hwnd, pwin);
         pwin->allocate(hwnd);

         // If main window pointer is NULL this
         // means that this window (which being created) is in fact main since
         // none still exists.
         if(pstate->m_pwnd_main == NULL)
            pstate->m_pwnd_main = pstate->m_pwnd;

         pwin->pre_subclass(hwnd);

         // dialog for now has its own procedure
         if(!pwin->is_dialog())
            WNDPROC old_wndproc = (WNDPROC)::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD_PTR)wnd_proc);

         // back to NULL at exit
         pstate->m_pwnd = NULL;
      }
   }
   return ::CallNextHookEx(pstate->m_old_hook, code, wParam, lParam);
}

inline bool event_router::check_exit_state(HWND hwnd, UINT message)
{
   state::state_ptr_type pstate = _the_module.get_thread_state();

   state::map_hwnd_to_window_type::iterator it_win = _the_module.m_window_map.find(hwnd);
   if(it_win == _the_module.m_window_map.end())
      return false;

   resources::win_handle* pwnd = it_win->second;
   if(pstate->m_pwnd_main == pwnd)
      return true;

   return false;
}

inline LRESULT event_router::handle_command_ui(UINT message, WPARAM wparam, LPARAM lparam)
{
   cmd_ui* pcmd = reinterpret_cast<cmd_ui*>(wparam);
   map_update_ui_t::iterator it_cmd = m_commands_ui.find(pcmd->get_cmd_id());

   if(it_cmd == m_commands_ui.end())
      return default_proc(handle(), message, wparam, lparam);

   update_ui_event_func_ptr_t p = (*it_cmd).second;
   sig_update_ui_event_t& fn = *p.get();

   fn(*pcmd);
   return do_not_route_further;
}

inline LRESULT event_router::handle_command(UINT message, WPARAM wparam, LPARAM lparam)
{
   command e(wparam, lparam);
   map_commands_t::iterator it_cmd = m_commands.find(e.lo_wparam());

   if(it_cmd == m_commands.end())
   {
      if(::IsWindow(_the_module.m_hmdiclient))
      {
         // try forwarding command message to any children
         HWND hclient = _the_module.m_hmdiclient;
         HWND hwnd = (HWND)::SendMessage(hclient, WM_MDIGETACTIVE, 0, 0);
         if(::IsWindow(hwnd))
         {
            LRESULT res = ::SendMessage(hwnd, message, wparam, lparam) ;
            if(lparam == disable_unhandled_commands && 
               res == do_not_route_further)
               return 0;
         }
      }

      // try Application object to handle command
      //thread::application* papp = _the_module.app();
      //if(papp)
      //{
    //     event_router* p = dynamic_cast<event_router*>(papp);
         //LRESULT r = papp->route(message, wparam, lparam);
         // Application is the last object to handle message
      //}

      return default_proc(handle(), message, wparam, lparam);
   }

   cmd_event_func_ptr_t p = (*it_cmd).second;
   sig_cmd_event_t& fn = *p.get();

   fn();
   return 0;
}

inline LRESULT event_router::handle_notification(UINT message, WPARAM wparam, LPARAM lparam)
{
   events::notification e(wparam, lparam);

   map_notify_t::iterator it = m_notifications.find(message);
   if(it == m_notifications.end())
      return default_proc(handle(), message, wparam, lparam);

   nfy_event_func_ptr_t p = (*it).second;
   sig_nfy_event_t& fn = *p.get();

   LRESULT ret_val = fn(e);
   return ret_val;
}

inline LRESULT event_router::handle_message(UINT message, WPARAM wparam, LPARAM lparam)
{
   events::message e(message, wparam, lparam);
   map_messages_t::iterator it = m_messages.find(message);
   if(it == m_messages.end())
      return default_proc(handle(), message, wparam, lparam);

   msg_event_func_ptr_t p = (*it).second;
   sig_msg_event_t& fn = *p.get();

   LRESULT ret_val = fn(e);
   return ret_val;
}

inline LRESULT event_router::route(UINT message, WPARAM wparam, LPARAM lparam)
{
   if(pre_translate_message(message, wparam, lparam))
      return 0;

   switch(message)
   {
   case WM_COMMAND:
   case WM_SYSCOMMAND:
      if(message == WM_COMMAND)
      {
         if(lparam == (LPARAM)update_ui_command)
            return handle_command_ui(message, wparam, lparam);
         else if(lparam == (LPARAM)disable_unhandled_commands)
         {
            cmd_ui* pcmd = reinterpret_cast<cmd_ui*>(wparam);

            if(pcmd->is_system_menu())
               return do_not_route_further;

            map_update_ui_t::iterator it_cmdui = m_commands_ui.find(pcmd->get_cmd_id());
            map_commands_t::iterator it_cmd = m_commands.find(pcmd->get_cmd_id());

            if(it_cmdui == m_commands_ui.end() &&
               it_cmd == m_commands.end())
            {
               pcmd->enable(false);
               return default_proc(handle(), message, wparam, lparam);
            }
            else
            {
               pcmd->enable(true);
               return do_not_route_further;
            }
         }
      }

      return handle_command(message, wparam, lparam);
   case WM_NOTIFY:
      return handle_notification(message, wparam, lparam);
   }

   return handle_message(message, wparam, lparam);
}

inline LRESULT CALLBACK event_router::wnd_proc(HWND hwnd, UINT message, WPARAM w, LPARAM l)
{
   if(message == WM_DESTROY)
   {
      if(check_exit_state(hwnd, message))
      {
         // if main window destroyed, then
         // exit program
         ::PostQuitMessage(0);
         return 0L;
      }
   }

   event_router* pwnd = dynamic_cast<event_router*>(_the_module.find_window(hwnd));

   if(pwnd == NULL)
      return ::DefWindowProc(hwnd, message, w, l);

   return pwnd->route(message, w, l);
}

   }
}
